import Utils from "./Utils.js";

export default class Algorithm {
    constructor() {
        if (Algorithm.instance) {
            return Algorithm.instance;
        }
        Algorithm.instance = this;

        this.utils = new Utils();

        this.threshold = 0.001;
        this.espgBase = -5000000;
        this.espgWidth = 10000000;
        this.cadWidth = 100000;
        this.valkyrie = this.utils.getLocalStorage('valkyrie');
    }

    unit(x, y) {
        this.valkyrie = {
            unit: 0.0,
            baseX: x - this.cadWidth / 2,
            baseY: y - this.cadWidth / 2,
            left: x - this.cadWidth / 2,
            right: x + this.cadWidth / 2,
            bottom: y - this.cadWidth / 2,
            top: y + this.cadWidth / 2
        };
        this.valkyrie.unit = this.espgWidth / this.cadWidth;
        this.utils.setLocalStorage('valkyrie', this.valkyrie);
    }

    pointInBox(x, y, left, right, bottom, top) {
        return x >= left && x <= right && y >= bottom && y <= top;
    }

    boxInBox(left, right, bottom, top) {
        return left >= this.valkyrie.left && right <= this.valkyrie.right
            && bottom >= this.valkyrie.bottom && top <= this.valkyrie.top;
    }

    normal(v) {
        return v * this.valkyrie.unit;
    }

    // denormal(v) {
    //     return v / this.valkyrie.unit;
    // }

    normalX(x) {
        return this.espgBase + (x - this.valkyrie.baseX) * this.valkyrie.unit;
    }

    normalY(y) {
        return this.espgBase + (y - this.valkyrie.baseY) * this.valkyrie.unit;
    }

    denormalX(x) {
        return this.valkyrie.baseX + (x - this.espgBase) / this.valkyrie.unit;
    }

    denormalY(y) {
        return this.valkyrie.baseY + (y - this.espgBase) / this.valkyrie.unit;
    }

        setConfig(config) {
        this.utils.setLocalStorage("config", config);
    }

    getConfig() {
        return this.utils.getLocalStorage("config");
    }

    // 圆弧
    arc(x, y, r, startAngle = 0, endAngle = 360) {
        let start_angle = startAngle, end_angle = endAngle;
        if (startAngle > endAngle) {
            start_angle = startAngle - 360;
        }

        const xys = [];
        for (let i = start_angle; i < end_angle; i++) {
            const _x = x + r * Math.cos((i * Math.PI) / 180);
            const _y = y + r * Math.sin((i * Math.PI) / 180);
            xys.push([_x, _y]);
        }
        if (endAngle === 360) {
            xys.push(xys[0]);
        }
        return xys;
    }

    // 椭圆
    ellipse(cx, cy, ux, uy, vx, vy, s = 0.0, e = 2 * Math.PI) {
        const xys = [];
        for (let i = s; i < e + 0.1; i = i + 0.1) {
            const _x = cx + Math.cos(i) * ux + Math.sin(i) * vx;
            const _y = cy + Math.cos(i) * uy + Math.sin(i) * vy;
            xys.push([_x, _y]);
        }
        xys.push(xys[0]);
        return xys;
    }

    // 斜率
    k(x1, y1, x2, y2) {
        return Math.atan((y2 - y1) / (x2 - x1));
    }

    // 计算点线段的垂线点: True-在线段上,False-不在线段上
    pointAtLine(p, ls, le) {
        const dx = ls[0] - le[0], dy = ls[1] - le[1], fp = [0, 0];
        if (Math.abs(dx) < this.threshold && Math.abs(dy) < this.threshold) {
            return { foot: p, flag: true }
        }

        let u = (p[0] - ls[0]) * (ls[0] - le[0]) + (p[1] - ls[1]) * (ls[1] - le[1]);
        u = u / ((dx * dx) + (dy * dy));
        fp[0] = ls[0] + u * dx;
        fp[1] = ls[1] + u * dy;

        const maxX =  ls[0] > le[0] ? ls[0] : le[0];
        const minX =  ls[0] < le[0] ? ls[0] : le[0];
        const maxY =  ls[1] > le[1] ? ls[1] : le[1];
        const minY =  ls[1] < le[1] ? ls[1] : le[1];

        let flag = false;
        if ((fp[0] <= maxX && fp[0] >= minX) && (fp[1] <= maxY && fp[1] >= minY)) {
            flag = true;
        }
        return { foot: fp, flag: flag }
    }

    // 判断点在直线的左右
    pointIsLineLR(p, start, end){
        const x = (start[0] - end[0]) / (start[1] - end[1]) * (p[1] - end[1]) + end[0];
        return x > p[0];
    }

    // 计算两点距离
    distance(x1,  y1,  x2,  y2){
        const dx = Math.floor(Math.abs(x1 - x2));
        const dy = Math.floor(Math.abs(y1 - y2));
        return Math.sqrt(dx * dx + dy * dy);
    }

    distances(points) {
        let length = 0;
        for (let i = 0; i < points.length - 1; i++) {
            const start = points[i], end = points[i + 1];
            length += this.distance(start[0], start[1], end[0], end[1]);
        }
        return length;
    }

    // 判断两点是否为同一个点
    isSame(x1, y1, x2, y2) {
        const dx = Math.abs(x1 - x2);
        const dy = Math.abs(y1 - y2);
        return dx <= this.threshold && dy <= this.threshold;
    }

    box(points) {
        const box = {
            left: points[0][0],
            right:points[0][0],
            bottom: points[0][1],
            top: points[0][1]
        };
        points.forEach((p) => {
            box.left =  p[0] < box.left ? p[0] : box.left;
            box.right = p[0] > box.right ? p[0] : box.right
            box.bottom = p[1] < box.bottom ? p[1] : box.bottom
            box.top = p[1] > box.top ? p[1] : box.top
        })

        return box
    }

    twist(x, y, angle) {
        const a = Math.PI * angle / 180;
        const _x = x * Math.cos(a) - y * Math.sin(a);
        const _y = x * Math.sin(a) + y * Math.cos(a);
        return [_x, _y];
    }

    lonlatTo2000(long, lat) {
        const to = '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=38500000 +y_0=0 +ellps=GRS80 +units=m +no_defs';
        const from = `+proj=longlat +datum=WGS84 +no_defs`;
        const coordinates = proj4(from, to, [long, lat]);
        coordinates[0] -= 38000000;
        return coordinates;
    }

    xy(start, end, len) {
        let x = 0, y = 0;
        if (Math.abs(start[0] - end[0]) < this.threshold) {
            // 斜率不存在的情况
            x = start[0]; y = start[1] + len;
            if (end[1] < start[1]) {
                y = start[1] - len;
            }
        }
        else {
            // 斜率存在的情况
            const k = this.k(start[0], start[1], end[0], end[1]);

            // 计算出可能在折线段上的两个坐标
            const x1 = start[0] + len * Math.cos(k);
            const y1 = start[1] + len * Math.sin(k);
            const x2 = start[0] + len * Math.cos(k + Math.PI);
            const y2 = start[1] + len * Math.sin(k + Math.PI);

            // 满足在折线段范围内的坐标点为正确的坐标
            if (x1 <= Math.max(start[0], end[0]) && x1 >= Math.min(start[0], end[0])) {
                x = x1; y = y1;
            }

            if (x2 <= Math.max(start[0], end[0]) && x2 > Math.min(start[0], end[0])) {
                x = x2; y = y2;
            }
        }
        return { x: x, y: y };
    }

    angle(grid, dir, left=false) {
        let result;
        if (dir === 0) {
            if (left) {
                result =  grid - Math.PI / 2;
            } else {
                result =  grid + Math.PI / 2;
            }
        } else {
            if (left) {
                result =  grid + Math.PI / 2;
            } else {
                result =  grid - Math.PI / 2;
            }
        }
        return result
    }

    xyd10(points, d10) {
        let temp = 0;
        const result = [0, 0];
        for (let i = 0; i < points.length - 1; i++) {
            const start = points[i], end = points[i + 1];
            const last = this.distance(start[0], start[1], end[0], end[1]);
            if (temp + last > d10) {
                const small = d10 - temp;
                const point = this.xy(start, end, small);
                result[0] = point.x; result[1] = point.y;
                temp += last;
                break;
            }
            temp += last;
        }

        if (temp < d10) {
            const index = points.length - 1;
            result[0] = points[index][0]; result[1] = points[index][1];
        }
        return result;
    }

    xyd100(pos, track, dir, d100) {
        const result = { dir: dir, dis: d100, x: 0, y: 0};
        for (let i = 0; i < track.length; i++) {
            const point = track[i];
            const distance = this.distance(pos.x, pos.y, point[0], point[1]);
            if (distance < this.threshold) {
                let tempLength = 0;
                if (dir === 0) {
                    if (i === track.length - 1) {
                        result.x = track[i][0]; result.y = track[i][1];
                        const lineStart = track[i - 1], lineEnd = track[i];
                        const dx = lineEnd[0] - lineStart[0], dy = lineEnd[1] - lineStart[1];
                        result.grid = Math.atan2(dy, dx);
                    } else {
                        for (let j = i; j < track.length - 1; j++) {
                            const lineStart = track[j], lineEnd = track[j + 1];
                            const lastLength = this.distance(lineStart[0], lineStart[1], lineEnd[0], lineEnd[1]);
                            if (tempLength + lastLength > d100) {
                                const smallLength = d100 - tempLength;
                                const p = this.xy(lineStart, lineEnd, smallLength);
                                const dx = lineEnd[0] - lineStart[0], dy = lineEnd[1] - lineStart[1];
                                result.x = p.x; result.y = p.y; result.grid = Math.atan2(dy, dx);
                                tempLength += lastLength;
                                break;
                            }
                            tempLength += lastLength;
                        }
                        if (tempLength < d100) {
                            const index = track.length - 1;
                            const lineStart = track[index - 1], lineEnd = track[index];
                            const dx = lineEnd[0] - lineStart[0], dy = lineEnd[1] - lineStart[1];
                            result.x = lineEnd[0]; result.y = lineEnd[1]; result.grid = Math.atan2(dy, dx);
                        }
                    }
                } else if (i === 0) {
                    result.x = track[0][0]; result.y = track[0][1];
                    const lineStart = track[1], lineEnd = track[0];
                    const dx = lineEnd[0] - lineStart[0], dy = lineEnd[1] - lineStart[1];
                    result.grid = Math.atan2(dy, dx);
                } else {
                    for (let j = i; j > 0; j--) {
                        const lineStart = track[j], lineEnd = track[j - 1];
                        const lastLength = this.distance(lineStart[0], lineStart[1], lineEnd[0], lineEnd[1]);
                        if (tempLength + lastLength > d100) {
                            const smallLength = d100 - tempLength;
                            const p = this.xy(lineStart, lineEnd, smallLength);
                            const dx = lineEnd[0] - lineStart[0], dy = lineEnd[1] - lineStart[1];
                            result.x = p.x; result.y = p.y; result.grid = Math.atan2(dy, dx);
                            tempLength += lastLength;
                            break;
                        }
                        tempLength += lastLength;
                    }
                    if (tempLength < d100) {
                        const lineStart = track[1], lineEnd = track[0];
                        const dx = lineEnd[0] - lineStart[0], dy = lineEnd[1] - lineStart[1];
                        result.x = lineEnd[0];
                        result.y = lineEnd[1];
                        result.grid = Math.atan2(dy, dx);
                    }
                }
                break;
            }
        }
        return result;
    }

    _route_same(start, end, tracks){
        const track = tracks.get(start.tid);
        let s = 0, e = 0;
        if (start.index <= end.index) {
            s = start.index + 1; e = end.index;
        } else {
            s = end.index + 1; e = start.index;
        }

        const points = [start.index <= end.index ? start.xy : end.xy];
        for (let i = s; i < e + 1; i++) {
            points.push(track.points[i]);
        }
        points.push(start.index > end.index ? start.xy : end.xy);

        let dir = null;
        if (start.index < end.index) dir = true;
        if (start.index > end.index) dir = false;

        return { points: points, finished: true, ids: [], dir: dir };
    }

    _route_finished(collector) {
        for (const c of collector) {
            if (!c.finished) {
                return false;
            }
        }
        return true;
    }

    route(s, e, tracks) {
        const startTrack = tracks.get(s.track_id);
        const endTrack = tracks.get(e.track_id);

        // 设置开始分站信息
        const start = { tid: s.track_id, index: 0, xy: [s.x, s.y] };
        for (let i = 0; i < startTrack.points.length; i++) {
            const point = startTrack.points[i];
            if (this.isSame(s.x, s.y, point[0], point[1])) {
                start.index = i;
                break;
            }
        }

        // 设置结束分站信息
        const end = { tid: e.track_id, index: 0, xy: [e.x, e.y] };
        for (let i = 0; i < endTrack.points.length; i++) {
            const point = endTrack.points[i];
            if (this.isSame(e.x, e.y, point[0], point[1])) {
                end.index = i;
                break;
            }
        }

        // 如果开始点和结束点在同一条轨迹线上
        if (start.tid === end.tid) {
            return this._route_same(start, end, tracks);
        }

        // 找到开始点的轨迹
        const track = tracks.get(start.tid);
        const forward = { points:[], finished: false, ids: [start.tid], dir: true };
        for (let i = start.index; i < track.points.length; i++) {
            forward.points.push(track.points[i]);
        }

        // 找到结束点的轨迹
        const reverse = { points:[], finished: false, ids: [start.tid], dir: false };
        for (let i = start.index; i >= 0; i--) {
            reverse.points.push(track.points[i]);
        }

        // 收集器用来收集所有能够向前探索的轨迹
        const collector = [];
        collector.push(forward);
        collector.push(reverse);

        let num = 2000;
        while (num > 0) {
            let flag = false;
            for (const c of collector) {
                // 这条轨迹已经完成了,则不做处理
                if (c.finished) continue;

                // 取一下结尾坐标
                const last = c.points[c.points.length - 1];

                // 查找所有的轨迹,看是否存在接续
                for (const t of tracks.values()) {
                    if (c.ids.includes(t.id)) continue;

                    // 取得开始点和结束点的位置
                    const sp = t.points[0], ep = t.points[t.points.length - 1];

                    // 和头部点进行比较,满足则直接返回
                    if (this.isSame(last[0], last[1], sp[0], sp[1])) {
                        // 如果和结束点所在的轨迹相同
                        if (t.id === end.tid) {
                            for (let i = 1; i < end.index + 1; i++) {
                                c.points.push(t.points[i]);
                            }
                            c.finished = true;
                            return c;
                        }

                        // 如果没有找到结束点的轨迹
                        c.ids.push(t.id);
                        const n = this.utils.deepcopy(c);
                        for (let i = 1; i < t.points.length; i++) {
                            n.points.push(t.points[i]);
                        }

                        // 如果探索距离超过1000米,这条轨迹不再探索
                        if (this.distances(n.points) > 2000) {
                            n.finished = true;
                        }

                        collector.push(n);
                        flag = true;
                        break;
                    }

                    // 和尾部点进行比较,满足则直接返回
                    if (this.isSame(last[0], last[1], ep[0], ep[1])) {
                        // 如果和结束点所在的轨迹相同
                        if (t.id === end.tid) {
                            for (let i = t.points.length - 2; i >= end.index; i--) {
                                c.points.push(t.points[i]);
                            }
                            c.finished = true;
                            return c;
                        }

                        // 如果没有找到结束点的轨迹
                        c.ids.push(t.id);
                        const n = this.utils.deepcopy(c);
                        for (let i = t.points.length - 2; i >= 0; i--) {
                            n.points.push(t.points[i]);
                        }

                        // 如果探索距离超过1000米,这条轨迹不再探索
                        if (this.distances(n.points) > 2000) {
                            n.finished = true;
                        }

                        collector.push(n);
                        flag = true;
                        break;
                    }
                }

                let done = true;
                for (const t of tracks.values()) {
                    if (!c.ids.includes(t.id)) {
                        done = false;
                        break;
                    }
                }
                // 如果所有的轨迹线都不匹配,则直接标记为完成
                if (done) c.finished = true;

                // 如果存在连接成功的情况
                if (flag) break;
            }

            // 判断是否所有的轨迹都完成
            if (this._route_finished(collector)) break;
            num -= 1;
        }

        // 如果实在找不到连线,则返回两个分站的连接线
        return { points: [start.xy, end.xy], finished: false, ids: [], dir: null };
    }
}